package election

import (
	"context"
	"errors"
	clientv3 "go.etcd.io/etcd/client/v3"
	"go.etcd.io/etcd/client/v3/concurrency"
	"go.uber.org/zap"
	"sync"
	"wzgames/pkg/logger"
)

type EtcdLeaderElection struct {
	LeaderElection
	session   *concurrency.Session
	election  *concurrency.Election
	ctx       context.Context
	cancel    context.CancelFunc
	eventchan chan MasterEvent

	key   string
	value string

	once sync.Once
}

func NewEtcdLeaderElection(ctx context.Context, client *clientv3.Client, leaseID clientv3.LeaseID, key, value string) (*EtcdLeaderElection, error) {
	ctx, cancel := context.WithCancel(ctx)
	//session, err := concurrency.NewSession(client, concurrency.WithContext(ctx), concurrency.WithLease(leaseID))
	session, err := concurrency.NewSession(client, concurrency.WithContext(ctx), concurrency.WithTTL(15))
	if err != nil {
		logger.Error("Couldn't create etcd session", zap.Error(err), zap.String("Key", key))
		cancel()
		return nil, err
	}

	election := concurrency.NewElection(session, key)

	return &EtcdLeaderElection{
		session:   session,
		election:  election,
		ctx:       ctx,
		cancel:    cancel,
		eventchan: make(chan MasterEvent, 2),
		key:       key,
		value:     value,
	}, nil
}

func (e *EtcdLeaderElection) Campaign() error {
	ctx, cancelFunc := context.WithCancel(e.ctx)
	defer cancelFunc()
	if err := e.election.Campaign(ctx, e.value); err != nil {
		//logger.Error("err == ", zap.String("error", err.Error()))
		e.eventchan <- MasterEvent{Type: MasterError, Error: err}
		return err
	}

slect:
	for {
		select {
		case res := <-e.election.Observe(ctx):
			if len(res.Kvs) > 0 {
				if string(res.Kvs[0].Value) == e.value {
					logger.Info("[Election] current node is be elected master")
					e.eventchan <- MasterEvent{Type: MasterAdded, Master: string(res.Kvs[0].Value)}
					break slect
				} else {
				}
			}
		}
	}
	// if select master
	for {
		select {
		case <-e.ctx.Done():
			return e.Resign()
		case <-e.session.Done():
			e.eventchan <- MasterEvent{Type: MasterError, Error: errors.New("elect: session expired")}
			return errors.New("elect: session expired")
		}
	}
}

func (e *EtcdLeaderElection) Resign() error {
	ctx, cancel := context.WithCancel(e.ctx)
	defer cancel()

	if err := e.election.Resign(ctx); err != nil {
		logger.Error("[Election] Failed to resign", zap.Error(err))
		return err
	}
	logger.Info("[Election] Resigned from leadership")
	return nil
}

func (e *EtcdLeaderElection) Close() {
	e.once.Do(func() {
		e.Resign()
		e.cancel()
		e.session.Close()
		close(e.eventchan)
	})
}

func (e *EtcdLeaderElection) EventsChan() <-chan MasterEvent {
	return e.eventchan
}
